/*
 *    This file is part of BlobDB.
 *
 *    BlobDB is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    BlobDB is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with BlobDB.  If not, see <http://www.gnu.org/licenses/>.
 *    
 *    Copyright(c) 2011 Michael Kankkonen
 *    http://www.vnetcon.org
 */


package org.vnetcon.blobdb.database.backend.file.fdo;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Properties;

import org.vnetcon.blobdb.database.SQLTransactionException;


public class TableData {

	boolean isWriteProtected = false;
	boolean isInTransaction = false;
	
    private String tableDataKey;
    private int tableDataID;
    private int tableID;
    private int rowCount;
    private int freeRows;
    private int lastRowid;
    private byte[] dataRows;
    private byte[] commitRows = null;
    private long touch;

    private String datadir = null;
    File f = null;
    RandomAccessFile rf = null;
    FileChannel rfchannel = null;
    FileLock fLock = null;
    
    public TableData(int tableDataID, int tableID){
    	this.tableDataID = tableDataID;
    	this.tableID = tableID;
		InputStream fIn = this.getClass().getResourceAsStream("/blobdb/blobdb.properties");
		BufferedReader bfIn = new BufferedReader(new InputStreamReader(fIn));
		Properties p = new Properties();
		try{
			p.load(bfIn);
			this.datadir = p.getProperty("database.dir", null);
			bfIn.close();
		}catch(Exception ex){
			ex.printStackTrace();
		}
		init();
    	PMF.hTableData.put(tableDataID, this);
    }
    
    private void init(){
    	try{
    		f = new File(this.datadir + "/" + "TableDataProps_" + this.tableID + "_" + this.tableDataID + ".dat");
    		if(f.exists()){
    			FileInputStream fIn = new FileInputStream(f);
    			DataInputStream dfIn = new DataInputStream(fIn);
    		    rowCount = dfIn.readInt();
    		    freeRows = dfIn.readInt();
    		    lastRowid = dfIn.readInt();
    			fIn.close();
    		}    		
    	}catch(Exception ex){
    		ex.printStackTrace();
    	}
    }

    private synchronized byte[] getTableBytes(boolean forWrite){
    	byte[] retBytes = null;
    	try{
    		f = new File(this.datadir + "/" + "TableDataRows_" + this.tableID + "_" + this.tableDataID + ".dat");
    		if(f.exists()){
    			long lFileLength = f.length();

				FileInputStream fIn = new FileInputStream(f);
				retBytes = new byte[(int) lFileLength];
				fIn.read(retBytes, 0, retBytes.length);
				fIn.close();    			

    			if(forWrite && rf == null){
   					rf = new RandomAccessFile(f, "rw");
   					rfchannel = rf.getChannel();
    				try{
    					fLock = rfchannel.tryLock();
    				}catch(Exception ex){
    					rfchannel.close();
    					rf.close();
    					rf = null;
    					rfchannel = null;
    					throw ex;
    				}
    			}
    			
    		}else{
//    			throw new Exception("No filebytes....");
    		}    		
    	}catch(Exception ex){
    		ex.printStackTrace();
    	}
    	return retBytes;
    }
    
    private synchronized void saveTableBytes(byte[] bytes){
    	try{
//    		File f = new File(this.datadir + "/" + "TableDataRows_" + this.tableID + "_" + this.tableDataID + ".dat");


    		if(rf == null){
    			rf = new RandomAccessFile(f, "rw");
    			rfchannel = rf.getChannel();
    			try{
    				rfchannel.tryLock();
        			rfchannel.close();
        			rf.close();
        			rf = null;
        			rfchannel = null;
    			}catch(Exception ex){
        			rfchannel.close();
        			rf.close();
        			rf = null;
        			rfchannel = null;
    				throw ex;
    			}
    		}else{
				fLock.release();
				rfchannel.close();
				rf.close();
				rf = null;
				rfchannel = null;
    		}

   			FileOutputStream fOut = new FileOutputStream(f);
   			DataOutputStream dfOut = null;
   			fOut.write(bytes);
   			fOut.flush();
   			fOut.getFD().sync();
   			fOut.close();
   			
    		File fProps = new File(this.datadir + "/" + "TableDataProps_" + this.tableID + "_" + this.tableDataID + ".dat");
   			fOut = new FileOutputStream(fProps);
   			dfOut = new DataOutputStream(fOut);
		    dfOut.writeInt(rowCount);
		    dfOut.writeInt(freeRows);
		    dfOut.writeInt(lastRowid);
   			dfOut.flush();
   			fOut.flush();
   			fOut.getFD().sync();
   			fOut.close();
   			
    	}catch(Exception ex){
    		ex.printStackTrace();
    	}
    }
    
    public void setTableDataKey(String tableDataKey){
    	this.tableDataKey = tableDataKey;
    }
    
	public int getTableID() {
		return tableID;
	}

	public byte[] getDataRows(boolean forWrite) throws SQLTransactionException {
		byte[] retbytes = null;
		if(this.isWriteProtected && forWrite){
			throw new SQLTransactionException("Block is currently write protected.");
		}
		
		retbytes = this.getTableBytes(forWrite);
		this.isWriteProtected = forWrite;
		return retbytes;
	}

	public void setDataRows(byte[] dataRowsParam) throws SQLTransactionException {
		if(!this.isWriteProtected){
			throw new SQLTransactionException("Can't update block without write protection.");
		}
		this.commitRows = dataRowsParam;
	}
	

	public String getTableDataKey() {
		return tableDataKey;
	}

	public int getRowCount() {
		return rowCount;
	}

	public void setRowCount(int rowCount) {
		this.rowCount = rowCount;
	}

	public int getFreeRows() {
		return freeRows;
	}

	public void setFreeRows(int freeRows) {
		this.freeRows = freeRows;
	}

	public int getLastRowid() {
		return lastRowid;
	}

	public void setLastRowid(int lastRowid) {
		this.lastRowid = lastRowid;
	}

	public int getTableDataID() {
		return tableDataID;
	}

	public void touch(long l){
		this.touch = l;
	}

	public void save() {
		if(this.commitRows != null){
			this.dataRows = this.commitRows;
		}
		this.saveTableBytes(this.dataRows);
		this.commitRows = null;
		this.isWriteProtected = false;
		this.isInTransaction = false;
		this.dataRows = null;
	}

	public void rollback(){
		
		if(rf != null){
			try {
				fLock.release();
				rf.close();
				rf = null;
				rfchannel = null;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		this.commitRows = null;
		this.isWriteProtected = false;
		this.isInTransaction = false;
	}
	
	public boolean isInTransaction() {
		return isInTransaction;
	}

	public void setInTransaction(boolean isInTransaction) {
		this.isInTransaction = isInTransaction;
	}
	

	public void finalize(){
		if(rf != null){
			try {
				fLock.release();
				rf.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}
